import "@site/src/languages/highlight";
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Using ClipNode for Masking Effects

In game development and graphics rendering, masking is a common technique used to control the visible area of an image or scene. In Dora SSR, we can easily implement various masking effects using the `ClipNode` class. This tutorial will guide you through using `ClipNode` to achieve shape-based and image-based masking effects with sample code.

## 1. What is ClipNode?

`ClipNode` is a scene node class in Dora SSR that allows you to set up a clipping mask to control the visible area of its child nodes. By specifying a mask node (`stencil`), `ClipNode` clips the rendering content of its child nodes based on the mask's shape or transparency.

### 1.1 Main Properties of ClipNode

- **stencil**: Defines the mask node that determines the clipping shape.
- **alphaThreshold**: The alpha threshold (ranging from 0 to 1) that determines which mask pixels participate in clipping. Clipping only applies when the mask pixel's alpha value exceeds this threshold.
- **inverted**: Inverts the clipping area. When set to `true`, the clipped and unclipped areas are swapped.

## 2. Example A: Using Any Shape as a Mask

In this first example, we will use a star-shaped mask to clip the visible area of an animated model.

### 2.1 Step-by-step Explanation

1. **Create the Vertex Data for a Star Shape**

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local function StarVertices(radius, line)
	-- Calculate the vertex coordinates of a star
	local a = math.rad(36)
	local c = math.rad(72)
	local f = math.sin(a) * math.tan(c) + math.cos(a)
	local R = radius
	local r = R / f
	local vecs = {}
	local count = 1
	for i = 9, line and -1 or 0, -1 do
		local angle = i * a
		local cr = i % 2 == 1 and r or R
		vecs[count] = Vec2(cr * math.sin(angle), cr * math.cos(angle))
		count = count + 1
	end
	-- Return the vertex array
	return vecs
end
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local function StarVertices(radius: number, line: integer): {Vec2.Type}
	-- Calculate the vertex coordinates of a star
	local a = math.rad(36)
	local c = math.rad(72)
	local f = math.sin(a) * math.tan(c) + math.cos(a)
	local R = radius
	local r = R / f
	local vecs: {Vec2.Type} = {}
	local count = 1
	for i = 9, line and -1 or 0, -1 do
		local angle = i * a
		local cr = i % 2 == 1 and r or R
		vecs[count] = Vec2(cr * math.sin(angle), cr * math.cos(angle))
		count = count + 1
	end
	-- Return the vertex array
	return vecs
end
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
function StarVertices(radius: number, line: number) {
	// Calculate the vertex coordinates of a star
	const a = math.rad(36);
	const c = math.rad(72);
	const f = math.sin(a) * math.tan(c) + math.cos(a);
	const R = radius;
	const r = R / f;
	const vecs: Vec2.Type[] = []
	for (let i = 9; line ? -1 : 0; i--) {
		const angle = i * a;
		const cr = i % 2 == 1 ? r : R
		vecs.push(Vec2(cr * math.sin(angle), cr * math.cos(angle)));
	}
	// Return the vertex array
	return vecs;
}
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
StarVertices = (radius, line) ->
	-- Calculate the vertex coordinates of a star
	a = math.rad 36
	c = math.rad 72
	f = math.sin(a) * math.tan(c) + math.cos a
	R = radius
	r = R / f
	vecs = []
	for i = 9, line and -1 or 0, -1
		angle = i * a
		cr = i % 2 == 1 and r or R
		vecs[] = Vec2 cr * math.sin(angle), cr * math.cos angle
	-- Return the vertex array
	vecs
```

</TabItem>
</Tabs>

2. **Draw the Mask Shape**

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local maskA = DrawNode()
maskA:drawPolygon(StarVertices(160, false))
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local maskA = DrawNode()
maskA:drawPolygon(StarVertices(160, false))
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
const maskA = DrawNode();
maskA.drawPolygon(StarVertices(160, false));
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
maskA = DrawNode()
maskA.drawPolygon StarVertices(160, false)
```

</TabItem>
</Tabs>

Here, we use `DrawNode` to draw a star shape as the mask node.

3. **Create an Animated Model**

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local targetA = Model("Model/xiaoli.model")
targetA.look = "happy"
targetA.fliped = true
-- Set model's animation and movement path
targetA:play("walk", true)
targetA:runAction(
	Sequence(
		X(1.5, -200, 200), Event("Turn"),
		X(1.5, 200, -200), Event("Turn")
	), true
)
targetA:slot("Turn", function()
	targetA.fliped = not targetA.fliped
end)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local targetA = Model("Model/xiaoli.model")
if not targetA is nil then
	targetA.look = "happy"
	targetA.fliped = true
	-- Set model's animation and movement path
	targetA:play("walk", true)
	targetA:runAction(
		Sequence(
			X(1.5, -200, 200), Event("Turn"),
			X(1.5, 200, -200), Event("Turn")
		), true
	)
	targetA:slot("Turn", function()
		targetA.fliped = not targetA.fliped
	end)
end
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
const targetA = Model("Model/xiaoli.model");
if (targetA) {
	targetA.look = "happy";
	targetA.fliped = true;
	// Set model's animation and movement path
	targetA.play("walk", true);
	targetA.runAction(
		Sequence(
			X(1.5, -200, 200), Event("Turn"),
			X(1.5, 200, -200), Event("Turn")
		), true
	);
	targetA.slot("Turn", () => {
		targetA.fliped = !targetA.fliped;
	});
}
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
targetA = with Model "Model/xiaoli.model"
	.look = "happy"
	.fliped = true
	-- Set model's animation and movement path
	.play "walk", true
	\runAction Sequence(
		X 1.5, -200, 200, Event "Turn",
		X 1.5, 200, -200, Event "Turn"
	), true
	\slot "Turn", ->
		targetA.fliped = not targetA.fliped
```

</TabItem>
</Tabs>

4. **Create a ClipNode and Add Child Nodes**

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local clipNodeA = ClipNode(maskA)
clipNodeA:addChild(targetA)
clipNodeA.inverted = true
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local clipNodeA = ClipNode(maskA)
clipNodeA:addChild(targetA)
clipNodeA.inverted = true
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
const clipNodeA = ClipNode(maskA);
clipNodeA.addChild(targetA);
clipNodeA.inverted = true;
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
clipNodeA = with ClipNode maskA
	\addChild targetA
	.inverted = true
```

</TabItem>
</Tabs>

We pass the mask node `maskA` to `ClipNode`, and add the animated model `targetA` as its child node.

5. **Add to the Scene**

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local exampleA = Node()
exampleA:addChild(clipNodeA)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local exampleA = Node()
exampleA:addChild(clipNodeA)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
const exampleA = Node();
exampleA.addChild(clipNodeA);
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
exampleA = with Node!
	\addChild clipNodeA
```

</TabItem>
</Tabs>

### Result

After running the above code, you will see the model visible only within the star shape, with the parts outside the star clipped away.

## 3. Example B: Using an Image with alphaThreshold for Masking

In the second example, we will use a model with transparency as the mask and utilize the `alphaThreshold` property to control the clipping effect.

### 3.1 Step-by-step Explanation

1. **Create the Mask Model**

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local maskB = Model("Model/xiaoli.model")
maskB.look = "happy"
maskB.fliped = true
maskB:play("walk", true)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local maskB = Model("Model/xiaoli.model")
if not maskB is nil then
	maskB.look = "happy"
	maskB.fliped = true
	maskB:play("walk", true)
end
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
const maskB = Model("Model/xiaoli.model");
if (maskB) {
	maskB.look = "happy";
	maskB.fliped = true;
	maskB.play("walk", true);
}
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
maskB = with Model "Model/xiaoli.model"
	.look = "happy"
	.fliped = true
	.play "walk", true
```

</TabItem>
</Tabs>

Here, we use a model as the mask node.

2. **Create the Target Shape**

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local targetB = DrawNode()
targetB:drawPolygon(StarVertices(160, false))
-- Set the movement path for the target shape
targetB:runAction(
	Sequence(
		X(1.5, -200, 200),
		X(1.5, 200, -200)
	), true
)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local targetB = DrawNode()
targetB:drawPolygon(StarVertices(160, false))
-- Set the movement path for the target shape
targetB:runAction(
	Sequence(
		X(1.5, -200, 200),
		X(1.5, 200, -200)
	), true
)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
const targetB = DrawNode();
targetB.drawPolygon(StarVertices(160, false));
// Set the movement path for the target shape
targetB.runAction(
	Sequence(
		X(1.5, -200, 200),
		X(1.5, 200, -200)
	), true
);
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
targetB = with DrawNode!
	\drawPolygon StarVertices 160, false
	-- Set the movement path for the target shape
	\runAction Sequence(
		X 1.5, -200, 200,
		X 1.5, 200, -200
	), true
```

</TabItem>
</Tabs>

3. **Create a ClipNode and Set alphaThreshold**

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local clipNodeB = ClipNode(maskB)
clipNodeB:addChild(targetB)
clipNodeB.inverted = true
clipNodeB.alphaThreshold = 0.3
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local clipNodeB = ClipNode(maskB)
clipNodeB:addChild(targetB)
clipNodeB.inverted = true
clipNodeB.alphaThreshold = 0.3
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
const clipNodeB = ClipNode(maskB);
clipNodeB.addChild(targetB);
clipNodeB.inverted = true;
clipNodeB.alphaThreshold = 0.3;
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
clipNodeB = with ClipNode maskB
	\addChild targetB
	.inverted = true
	.alphaThreshold = 0.3
```

</TabItem>
</Tabs>

By setting `alphaThreshold = 0.3`, clipping will only apply when the mask model's pixel alpha value is greater than 0.3.

4. **Add to the Scene**

<Tabs groupId="language-select">
<TabItem value="lua" label="Lua">

```lua
local exampleB = Node()
exampleB:addChild(clipNodeB)
```

</TabItem>
<TabItem value="tl" label="Teal">

```tl
local exampleB = Node()
exampleB:addChild(clipNodeB)
```

</TabItem>
<TabItem value="ts" label="TypeScript">

```ts
const exampleB = Node();
exampleB.addChild(clipNodeB);
```

</TabItem>
<TabItem value="yue" label="YueScript">

```yue
exampleB = with Node!
	\addChild clipNodeB
```

</TabItem>
</Tabs>

### 3.2 Result

After running the code, you will notice that the target shape is visible only in the non-transparent parts of the mask model, creating a transparency-based masking effect.

## 4. Conclusion

Through these two examples, we've learned how to use `ClipNode` in Dora SSR to implement different masking effects:

- **Example A** showed how to use any drawn shape as a mask to clip the display area of child nodes.
- **Example B** demonstrated how to use the `alphaThreshold` property to clip using an image or model with transparency.

### 4.1 Tips

- **Using alphaThreshold**: When the mask node contains semi-transparent areas, `alphaThreshold` is useful. It allows you to finely control which pixels participate in clipping.
- **Inverted Property**: By toggling `inverted`, you can easily reverse the clipping area to achieve different visual effects.

We hope this tutorial helps you better understand and apply `ClipNode` in Dora SSR to create diverse masking effects. For any questions, feel free to consult the official documentation or join community discussions.
